package resolver

/*
Copyright 2021-2025 The k8gb Contributors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Generated by GoLic, for more details see: https://github.com/AbsaOSS/golic
*/

import (
	"context"
	"fmt"
	"os"
	"strings"
	"testing"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"

	"github.com/k8gb-io/k8gb/api/v1beta1"
	"github.com/k8gb-io/k8gb/controllers/utils"

	"github.com/rs/zerolog"
	"github.com/stretchr/testify/assert"
	corev1 "k8s.io/api/core/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

func TestDepResolver(t *testing.T) {
	var tests = []struct {
		name         string
		envvars      string
		actAndAssert func(t *testing.T)
	}{
		{
			name: "resolver is consistent: running multiple times",
			envvars: `CLUSTER_GEO_TAG=us;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=local.test;
				 EXTDNS_ENABLED=true`,
			actAndAssert: func(t *testing.T) {
				cfg, err := NewResolver().ResolveOperatorConfig()
				assert.NoError(t, err)
				assert.Equal(t, "us", cfg.ClusterGeoTag)
				assert.Equal(t, 53, cfg.FallbackEdgeDNSServerPortRaw)
				assert.Equal(t, "k8gb", cfg.K8gbNamespace)
				assert.Equal(t, corev1.ServiceTypeClusterIP, cfg.CoreDNSServiceType)
				assert.Equal(t, "", cfg.Infoblox.Host)
				assert.Equal(t, SimpleFormat, cfg.Log.Format)
				assert.Equal(t, zerolog.InfoLevel, cfg.Log.Level)
				assert.Equal(t, 0, len(cfg.DelegationZones[0].ExtClusterNSNames))
				assert.Equal(t, DNSTypeExternal, cfg.EdgeDNSType)
				assert.Equal(t, "0.0.0.0:8080", cfg.MetricsAddress)

				cfg, err = NewResolver().ResolveOperatorConfig()
				assert.NoError(t, err)
				assert.Equal(t, "us", cfg.ClusterGeoTag)
				assert.Equal(t, 53, cfg.FallbackEdgeDNSServerPortRaw)
				assert.Equal(t, "k8gb", cfg.K8gbNamespace)
				assert.Equal(t, corev1.ServiceTypeClusterIP, cfg.CoreDNSServiceType)
				assert.Equal(t, "", cfg.Infoblox.Host)
				assert.Equal(t, SimpleFormat, cfg.Log.Format)
				assert.Equal(t, zerolog.InfoLevel, cfg.Log.Level)
				assert.Equal(t, 0, len(cfg.DelegationZones[0].ExtClusterNSNames))
				assert.Equal(t, DNSTypeExternal, cfg.EdgeDNSType)
				assert.Equal(t, "0.0.0.0:8080", cfg.MetricsAddress)

				_ = os.Setenv("CLUSTER_GEO_TAG", "xy")
				cfg, err = NewResolver().ResolveOperatorConfig()
				assert.NoError(t, err)
				assert.Equal(t, "xy", cfg.ClusterGeoTag)
				assert.Equal(t, 53, cfg.FallbackEdgeDNSServerPortRaw)
				assert.Equal(t, "k8gb", cfg.K8gbNamespace)
				assert.Equal(t, corev1.ServiceTypeClusterIP, cfg.CoreDNSServiceType)
				assert.Equal(t, "", cfg.Infoblox.Host)
				assert.Equal(t, SimpleFormat, cfg.Log.Format)
				assert.Equal(t, zerolog.InfoLevel, cfg.Log.Level)
				assert.Equal(t, 0, len(cfg.DelegationZones[0].ExtClusterNSNames))
				assert.Equal(t, DNSTypeExternal, cfg.EdgeDNSType)
				assert.Equal(t, "0.0.0.0:8080", cfg.MetricsAddress)

			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			// arrange
			cleanup := setEnvVarsFromString(test.envvars)
			defer cleanup()
			// removing args given by test, otherwise kong start to parse them
			os.Args = []string{os.Args[0]}
			test.actAndAssert(t)
		})
	}
}

func TestSpec(t *testing.T) {
	var tests = []struct {
		name                string
		spec                v1beta1.GslbSpec
		expectedError       bool
		expectedUpdateCount int
		getClient           func(*v1beta1.Gslb) *SpyClient
	}{
		{
			name: "valid Spec 1",
			spec: v1beta1.GslbSpec{
				Strategy: v1beta1.Strategy{
					DNSTtlSeconds: DefaultTTLSeconds,
					Type:          FailoverStrategy,
					PrimaryGeoTag: "us",
				},
			},
			expectedUpdateCount: 1,
			getClient: func(gslb *v1beta1.Gslb) *SpyClient {
				return NewSpyClient(gslb, func(_ client.Object) {})
			},
		},
		{
			name: "valid Spec 2 - empty DNSTTLSeconds",
			spec: v1beta1.GslbSpec{
				Strategy: v1beta1.Strategy{
					DNSTtlSeconds: 0,
					Type:          FailoverStrategy,
					PrimaryGeoTag: "us",
				},
			},
			expectedUpdateCount: 1,
			getClient: func(gslb *v1beta1.Gslb) *SpyClient {
				return NewSpyClient(gslb, func(obj client.Object) {
					gslb := obj.(*v1beta1.Gslb)
					assert.Equal(t, DefaultTTLSeconds, gslb.Spec.Strategy.DNSTtlSeconds)
				})
			},
		},
		{
			name: "valid Spec 3 weight",
			spec: v1beta1.GslbSpec{
				Strategy: v1beta1.Strategy{
					DNSTtlSeconds: 0,
					Type:          RoundRobinStrategy,
					PrimaryGeoTag: "us",
					Weight: map[string]int{
						"eu": 35,
						"us": 50,
						"za": 15,
					},
				},
			},
			expectedUpdateCount: 1,
			getClient: func(gslb *v1beta1.Gslb) *SpyClient {
				return NewSpyClient(gslb, func(_ client.Object) {})
			},
		},
		{
			name: "invalid Spec 2 out of Type",
			spec: v1beta1.GslbSpec{
				Strategy: v1beta1.Strategy{
					DNSTtlSeconds: 0,
					Type:          "Invalid",
					PrimaryGeoTag: "us",
				},
			},
			expectedError:       true,
			expectedUpdateCount: 0,
			getClient: func(gslb *v1beta1.Gslb) *SpyClient {
				return NewSpyClient(gslb, func(_ client.Object) {})
			},
		},
		{
			name: "invalid Spec 3 weight",
			spec: v1beta1.GslbSpec{
				Strategy: v1beta1.Strategy{
					DNSTtlSeconds: 0,
					Type:          RoundRobinStrategy,
					PrimaryGeoTag: "us",
					Weight: map[string]int{
						"eu": 10010,
						"us": 50,
						"za": 15,
					},
				},
			},
			expectedError:       true,
			expectedUpdateCount: 0,
			getClient: func(gslb *v1beta1.Gslb) *SpyClient {
				return NewSpyClient(gslb, func(_ client.Object) {})
			},
		},
		{
			name: "valid Spec 4 missing geotag",
			spec: v1beta1.GslbSpec{
				Strategy: v1beta1.Strategy{
					DNSTtlSeconds: 0,
					Type:          RoundRobinStrategy,
				},
			},
			expectedUpdateCount: 1,
			getClient: func(gslb *v1beta1.Gslb) *SpyClient {
				return NewSpyClient(gslb, func(_ client.Object) {})
			},
		},
		{
			name: "invalid Spec 4 weight",
			spec: v1beta1.GslbSpec{
				Strategy: v1beta1.Strategy{
					DNSTtlSeconds: 0,
					Type:          RoundRobinStrategy,
					PrimaryGeoTag: "us",
					Weight: map[string]int{
						"eu#": 35,
						"us":  50,
						"za":  15,
					},
				},
			},
			expectedError:       true,
			expectedUpdateCount: 0,
			getClient: func(gslb *v1beta1.Gslb) *SpyClient {
				return NewSpyClient(gslb, func(_ client.Object) {})
			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {

			// arrange
			gslb := &v1beta1.Gslb{ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"}, Spec: test.spec}
			cl := test.getClient(gslb)

			// act
			err := NewResolver().ResolveGslbSpec(context.TODO(), gslb, cl)

			// assert
			if test.expectedError {
				assert.Error(t, err)
				return
			}
			assert.Equal(t, test.expectedUpdateCount, cl.UpdateCount)
			assert.NoError(t, err)
		})
	}
}

func TestConfigurations(t *testing.T) {
	var tests = []struct {
		name          string
		envvars       string
		assert        func(t *testing.T, cfg *Config)
		expectedError bool
	}{
		{
			name: "minimal valid configuration",
			envvars: `CLUSTER_GEO_TAG=us;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=local.test;
				 EXTDNS_ENABLED=true`,
			assert: func(t *testing.T, cfg *Config) {
				assert.Equal(t, "us", cfg.ClusterGeoTag)
				assert.Equal(t, 53, cfg.FallbackEdgeDNSServerPortRaw)
				assert.Equal(t, "k8gb", cfg.K8gbNamespace)
				assert.Equal(t, corev1.ServiceTypeClusterIP, cfg.CoreDNSServiceType)
				assert.Equal(t, "", cfg.Infoblox.Host)
				assert.Equal(t, SimpleFormat, cfg.Log.Format)
				assert.Equal(t, zerolog.InfoLevel, cfg.Log.Level)
				assert.Equal(t, 0, len(cfg.DelegationZones[0].ExtClusterNSNames))
				assert.Equal(t, DNSTypeExternal, cfg.EdgeDNSType)
				assert.Equal(t, "0.0.0.0:8080", cfg.MetricsAddress)
				assert.Equal(t, 0, len((&Resolver{}).GetDeprecations()))
				assert.False(t, cfg.HasExtClusterGeoTags())
			},
		},
		{
			name: "minimal valid configuration with external geotags",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=local.test;
				 EXT_GSLB_CLUSTERS_GEO_TAGS=za,eu`,
			assert: func(t *testing.T, cfg *Config) {
				m := map[string]string{"eu": "gslb-ns-eu-cloud.example.com", "za": "gslb-ns-za-cloud.example.com"}
				assert.Equal(t, "us-1", cfg.ClusterGeoTag)
				assert.Equal(t, 53, cfg.FallbackEdgeDNSServerPortRaw)
				assert.Equal(t, "k8gb", cfg.K8gbNamespace)
				assert.Equal(t, corev1.ServiceTypeClusterIP, cfg.CoreDNSServiceType)
				assert.Equal(t, "", cfg.Infoblox.Host)
				assert.Equal(t, SimpleFormat, cfg.Log.Format)
				assert.Equal(t, zerolog.InfoLevel, cfg.Log.Level)
				assert.True(t, utils.EqualAnnotations(m, cfg.DelegationZones[0].ExtClusterNSNames))
				assert.True(t, cfg.HasExtClusterGeoTags())
			},
		},
		{
			name:          "invalid configuration with invalid external cluster geotag",
			expectedError: true,
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=local.test;
				 EXT_GSLB_CLUSTERS_GEO_TAGS=za*,eu`,
		},
		{
			name:          "invalid configuration with non-unique external cluster geotag",
			expectedError: true,
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=local.test;
				 EXT_GSLB_CLUSTERS_GEO_TAGS=za,eu,za`,
		},
		{
			name: "valid configuration with multiple parent DNS servers",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=local.test,10.10.0.2:5353,10.10.0.2`,
			assert: func(t *testing.T, cfg *Config) {
				assert.Equal(t, "local.test", cfg.ParentZoneDNSServers[0].Host)
				assert.Equal(t, 53, cfg.ParentZoneDNSServers[0].Port)
				assert.Equal(t, "10.10.0.2", cfg.ParentZoneDNSServers[1].Host)
				assert.Equal(t, 5353, cfg.ParentZoneDNSServers[1].Port)
				assert.Equal(t, "10.10.0.2", cfg.ParentZoneDNSServers[2].Host)
				assert.Equal(t, 53, cfg.ParentZoneDNSServers[2].Port)
			},
		},
		{
			name: "invalid configuration with multiple parent DNS servers 1",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=local.test,10.10.0.2:500353,10.10.0.2`,
			expectedError: true,
		},
		{
			name: "invalid configuration with multiple parent DNS servers 2",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=local.test,10.10.0.2:badport,10.10.0.2`,
			expectedError: true,
		},
		{
			name: "invalid configuration with multiple parent DNS servers 3",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=:5053,10.10.0.2`,
			expectedError: true,
		},
		{
			name: "support fallback parent DNS",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVER=10.10.0.2;
				 EDGE_DNS_SERVER_PORT=5053`,
			assert: func(t *testing.T, cfg *Config) {
				assert.Equal(t, "10.10.0.2", cfg.ParentZoneDNSServers[0].Host)
				assert.Equal(t, 5053, cfg.ParentZoneDNSServers[0].Port)
				assert.Equal(t, 2, len((&Resolver{}).GetDeprecations()))
			},
		},
		{
			name: "support fallback parent DNS 2",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVER=10.10.0.2;`,
			assert: func(t *testing.T, cfg *Config) {
				assert.Equal(t, "10.10.0.2", cfg.ParentZoneDNSServers[0].Host)
				assert.Equal(t, 53, cfg.ParentZoneDNSServers[0].Port)
				assert.Equal(t, 1, len((&Resolver{}).GetDeprecations()))
			},
		},
		{
			name: "invalid fallback parent DNS",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVER=10.10.0.2;
				 EDGE_DNS_SERVER_PORT=0`,
			expectedError: true,
		},
		{
			name: "ignored fallback parent DNS",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVER=10.10.0.2;
				 EDGE_DNS_SERVER_PORT=3535;
				 EDGE_DNS_SERVERS=local.test,10.10.0.2:5353,10.10.0.2`,
			assert: func(t *testing.T, cfg *Config) {
				assert.Equal(t, "local.test", cfg.ParentZoneDNSServers[0].Host)
				assert.Equal(t, 53, cfg.ParentZoneDNSServers[0].Port)
				assert.Equal(t, "10.10.0.2", cfg.ParentZoneDNSServers[1].Host)
				assert.Equal(t, 5353, cfg.ParentZoneDNSServers[1].Port)
				assert.Equal(t, "10.10.0.2", cfg.ParentZoneDNSServers[2].Host)
				assert.Equal(t, 53, cfg.ParentZoneDNSServers[2].Port)
				assert.Equal(t, 3, len(cfg.ParentZoneDNSServers))
			},
		},
		{
			name: "multiple DNS providers",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EXTDNS_ENABLED=true;
				 EDGE_DNS_SERVERS=dns.test;
				 INFOBLOX_GRID_HOST="infoblox.test"`,
			expectedError: true,
		},
		{
			name: "valid metrics address",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=dns.test;
				 METRICS_ADDRESS=10.0.0.2:8080`,
			assert: func(t *testing.T, cfg *Config) {
				assert.Equal(t, "10.0.0.2:8080", cfg.MetricsAddress)
			},
		},
		{
			name: "invalid metrics address",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=dns.test;
				 METRICS_ADDRESS=10.0.0.:8080`,
			expectedError: true,
		},
		{
			name: "valid infoblox 1",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=dns.test;
				 INFOBLOX_GRID_HOST="infoblox.test";
				 INFOBLOX_WAPI_PORT=5053;
				 INFOBLOX_WAPI_USERNAME="testadmin";
				 INFOBLOX_WAPI_PASSWORD="testpassword";
				 INFOBLOX_WAPI_VERSION="v2";
				 INFOBLOX_HTTP_REQUEST_TIMEOUT="30";
				 INFOBLOX_HTTP_POOL_CONNECTIONS="100"`,
			assert: func(t *testing.T, cfg *Config) {
				assert.Equal(t, "infoblox.test", cfg.Infoblox.Host)
				assert.Equal(t, 5053, cfg.Infoblox.Port)
				assert.Equal(t, "testadmin", cfg.Infoblox.Username)
				assert.Equal(t, "testpassword", cfg.Infoblox.Password)
				assert.Equal(t, "v2", cfg.Infoblox.Version)
				assert.Equal(t, 30, cfg.Infoblox.HTTPRequestTimeout)
				assert.Equal(t, 100, cfg.Infoblox.HTTPPoolConnections)
			},
		},
		{
			name: "valid infoblox 2",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=dns.test;
				 INFOBLOX_GRID_HOST="192.168.10.10";
				 INFOBLOX_WAPI_USERNAME="testadmin";
				 INFOBLOX_WAPI_PASSWORD="testpassword"`,
			assert: func(t *testing.T, cfg *Config) {
				assert.Equal(t, "192.168.10.10", cfg.Infoblox.Host)
				assert.Equal(t, 53, cfg.Infoblox.Port)
				assert.Equal(t, "testadmin", cfg.Infoblox.Username)
				assert.Equal(t, "testpassword", cfg.Infoblox.Password)
				assert.Equal(t, "v2", cfg.Infoblox.Version)
				assert.Equal(t, 20, cfg.Infoblox.HTTPRequestTimeout)
				assert.Equal(t, 10, cfg.Infoblox.HTTPPoolConnections)
			},
		},
		{
			name: "valid infoblox 3 - empty INFOBLOX_GRID_HOST",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=dns.test;
				 INFOBLOX_GRID_HOST="";
				 INFOBLOX_WAPI_USERNAME="testadmin";
				 INFOBLOX_WAPI_PASSWORD="testpassword"`,
			assert: func(t *testing.T, cfg *Config) {
				assert.Equal(t, DNSTypeNoEdgeDNS, cfg.EdgeDNSType)
			},
		},
		{
			name: "invalid infoblox 1 - INFOBLOX_GRID_HOST",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=dns.test;
				 INFOBLOX_GRID_HOST="192.168.10.10.";
				 INFOBLOX_WAPI_USERNAME="testadmin";
				 INFOBLOX_WAPI_PASSWORD="testpassword"`,
			expectedError: true,
		},
		{
			name: "invalid infoblox 2 - INFOBLOX_WAPI_USERNAME",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=dns.test;
				 INFOBLOX_GRID_HOST="192.168.10.10.";
				 INFOBLOX_WAPI_PASSWORD="testpassword"`,
			expectedError: true,
		},
		{
			name: "invalid infoblox 3 - INFOBLOX_WAPI_USERNAME",
			envvars: `CLUSTER_GEO_TAG=us-1;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=dns.test;
				 INFOBLOX_GRID_HOST="192.168.10.10.";
				 INFOBLOX_WAPI_PASSWORD="testpassword"`,
			expectedError: true,
		},
		{
			name: "valid configuration without clusterGeotag in EXT_GSLB_CLUSTERS_GEO_TAGS",
			envvars: `CLUSTER_GEO_TAG=us;
				 EXT_GSLB_CLUSTERS_GEO_TAGS=eu;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=local.test;
				 EXTDNS_ENABLED=true`,
			assert: func(t *testing.T, cfg *Config) {
				assert.Equal(t, "gslb-ns-us-cloud.example.com", cfg.DelegationZones[0].ClusterNSName)
				assert.Equal(t, "gslb-ns-eu-cloud.example.com", cfg.DelegationZones[0].ExtClusterNSNames["eu"])
			},
		},
		{
			name: "valid configuration with clusterGeotag in EXT_GSLB_CLUSTERS_GEO_TAGS",
			envvars: `CLUSTER_GEO_TAG=us;
				 EXT_GSLB_CLUSTERS_GEO_TAGS=us,eu;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=local.test;
				 EXTDNS_ENABLED=true`,
			assert: func(t *testing.T, cfg *Config) {
				assert.Equal(t, "gslb-ns-us-cloud.example.com", cfg.DelegationZones[0].ClusterNSName)
				assert.Equal(t, "gslb-ns-eu-cloud.example.com", cfg.DelegationZones[0].ExtClusterNSNames["eu"])
				assert.Equal(t, "", cfg.DelegationZones[0].ExtClusterNSNames["us"])
			},
		},
		{
			name: "invalid configuration with multiple clusterGeotag in EXT_GSLB_CLUSTERS_GEO_TAGS",
			envvars: `CLUSTER_GEO_TAG=us;
				 EXT_GSLB_CLUSTERS_GEO_TAGS=us,us,eu;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=local.test;
				 EXTDNS_ENABLED=true`,
			expectedError: true,
		},
		{
			name: "invalid configuration with equal clusterGeotag in EXT_GSLB_CLUSTERS_GEO_TAGS",
			envvars: `CLUSTER_GEO_TAG=us;
				 EXT_GSLB_CLUSTERS_GEO_TAGS=us;
				 DNS_ZONES=example.com:cloud.example.com:300;
				 EDGE_DNS_SERVERS=local.test;
				 EXTDNS_ENABLED=true`,
			assert: func(t *testing.T, cfg *Config) {
				assert.Equal(t, "gslb-ns-us-cloud.example.com", cfg.DelegationZones[0].ClusterNSName)
				assert.Equal(t, 0, len(cfg.DelegationZones[0].ExtClusterNSNames))
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			// arrange
			cleanup := setEnvVarsFromString(test.envvars)
			defer cleanup()
			// removing args given by test, otherwise kong start to parse them
			os.Args = []string{os.Args[0]}

			// act
			cfg, err := NewResolver().ResolveOperatorConfig()
			if test.expectedError {
				assert.Error(t, err)
				return
			}

			// assert
			test.assert(t, cfg)
		})
	}
}

//nolint:goconst
func TestParseDNSZones(t *testing.T) {
	str220 := `aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1.
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb2.
cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc3.eee`
	str64 := "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1"

	contains := func(dzi []*DelegationZoneInfo, compare func(info *DelegationZoneInfo) bool) bool {
		for _, v := range dzi {
			if compare(v) {
				return true
			}
		}
		return false
	}
	tests := []struct {
		name        string
		expectedLen int
		config      *Config
		assert      func(zoneInfo []*DelegationZoneInfo, err error)
	}{
		{
			name: "invalid negTTL",
			config: &Config{
				DNSZones:              "example.com:cloud.example.com:30x",
				ParentZoneDNSServers:  []utils.DNSServer{{Host: "edge.com", Port: 53}},
				ClusterGeoTag:         "us",
				ExtClustersGeoTagsRaw: []string{"za", "eu"},
			},
			expectedLen: 0,
			assert: func(_ []*DelegationZoneInfo, err error) {
				assert.Error(t, err)
			},
		},
		{
			name: "multiple zones",
			config: &Config{
				DNSZones:              "example.com:cloud.example.com:30;example.io:cloud.example.io:50",
				ParentZoneDNSServers:  []utils.DNSServer{{Host: "edge.com", Port: 53}},
				ClusterGeoTag:         "us",
				ExtClustersGeoTagsRaw: []string{"za", "eu"},
			},
			expectedLen: 2,
			assert: func(zoneInfo []*DelegationZoneInfo, err error) {
				assert.NoError(t, err)
				assert.True(t, contains(zoneInfo, func(info *DelegationZoneInfo) bool {
					return info.ParentZone == "example.com" && info.LoadBalancedZone == "cloud.example.com"
				}))
				assert.True(t, contains(zoneInfo, func(info *DelegationZoneInfo) bool {
					return info.ParentZone == "example.io" && info.LoadBalancedZone == "cloud.example.io"
				}))
				assert.True(t, contains(zoneInfo, func(info *DelegationZoneInfo) bool {
					return info.ClusterNSName == "gslb-ns-us-cloud.example.com" &&
						info.GetNSServerList()[0] == "gslb-ns-eu-cloud.example.com" &&
						info.GetNSServerList()[1] == "gslb-ns-us-cloud.example.com" &&
						info.GetNSServerList()[2] == "gslb-ns-za-cloud.example.com" &&
						info.GetExternalDNSEndpointName() == "k8gb-ns-extdns-cloud-example-com"
				}))
				assert.True(t, contains(zoneInfo, func(info *DelegationZoneInfo) bool {
					return info.ClusterNSName == "gslb-ns-us-cloud.example.io" &&
						info.GetNSServerList()[0] == "gslb-ns-eu-cloud.example.io" &&
						info.GetNSServerList()[1] == "gslb-ns-us-cloud.example.io" &&
						info.GetNSServerList()[2] == "gslb-ns-za-cloud.example.io" &&
						info.GetExternalDNSEndpointName() == "k8gb-ns-extdns-cloud-example-io"
				}))
			},
		},
		{
			name: "multiple domains",
			config: &Config{
				DNSZones:              "example.com:cloud.example.com:30;example.com:pair.example.com:50",
				ParentZoneDNSServers:  []utils.DNSServer{{Host: "edge.com", Port: 53}},
				ClusterGeoTag:         "us",
				ExtClustersGeoTagsRaw: []string{"za", "eu"},
			},
			expectedLen: 2,
			assert: func(zoneInfo []*DelegationZoneInfo, err error) {
				assert.NoError(t, err)
				assert.True(t, contains(zoneInfo, func(info *DelegationZoneInfo) bool {
					return info.ParentZone == "example.com" && info.LoadBalancedZone == "cloud.example.com"
				}))
				assert.True(t, contains(zoneInfo, func(info *DelegationZoneInfo) bool {
					return info.ParentZone == "example.com" && info.LoadBalancedZone == "pair.example.com"
				}))
				assert.True(t, contains(zoneInfo, func(info *DelegationZoneInfo) bool {
					return info.ClusterNSName == "gslb-ns-us-cloud.example.com" &&
						info.GetNSServerList()[0] == "gslb-ns-eu-cloud.example.com" &&
						info.GetNSServerList()[1] == "gslb-ns-us-cloud.example.com" &&
						info.GetNSServerList()[2] == "gslb-ns-za-cloud.example.com" &&
						info.GetExternalDNSEndpointName() == "k8gb-ns-extdns-cloud-example-com"
				}))
				assert.True(t, contains(zoneInfo, func(info *DelegationZoneInfo) bool {
					return info.ClusterNSName == "gslb-ns-us-pair.example.com" &&
						info.GetNSServerList()[0] == "gslb-ns-eu-pair.example.com" &&
						info.GetNSServerList()[1] == "gslb-ns-us-pair.example.com" &&
						info.GetNSServerList()[2] == "gslb-ns-za-pair.example.com" &&
						info.GetExternalDNSEndpointName() == "k8gb-ns-extdns-pair-example-com"
				}))
			},
		},
		{
			name: "ends with semicolon",
			config: &Config{
				DNSZones:              "example.com:cloud.example.com:30;example.io:cloud.example.io:300;",
				ParentZoneDNSServers:  []utils.DNSServer{{Host: "edge.com", Port: 53}},
				ClusterGeoTag:         "us",
				ExtClustersGeoTagsRaw: []string{"za", "eu"},
			},
			expectedLen: 2,
			assert: func(zoneInfo []*DelegationZoneInfo, err error) {
				assert.NoError(t, err)
				assert.True(t, contains(zoneInfo, func(info *DelegationZoneInfo) bool {
					return info.ParentZone == "example.com" && info.LoadBalancedZone == "cloud.example.com"
				}))
				assert.True(t, contains(zoneInfo, func(info *DelegationZoneInfo) bool {
					return info.ParentZone == "example.io" && info.LoadBalancedZone == "cloud.example.io"
				}))
			},
		},
		{
			name: "trimmed spaces and semicolons",
			config: &Config{
				DNSZones:              "example.com: cloud.example.com: 50; example.io:cloud.example.io: 30 ;",
				ParentZoneDNSServers:  []utils.DNSServer{{Host: "edge.com", Port: 53}},
				ClusterGeoTag:         "us",
				ExtClustersGeoTagsRaw: []string{"za", "eu"},
			},
			expectedLen: 2,
			assert: func(zoneInfo []*DelegationZoneInfo, err error) {
				assert.NoError(t, err)
				assert.True(t, contains(zoneInfo, func(info *DelegationZoneInfo) bool {
					return info.ParentZone == "example.com" && info.LoadBalancedZone == "cloud.example.com"
				}))
				assert.True(t, contains(zoneInfo, func(info *DelegationZoneInfo) bool {
					return info.ParentZone == "example.io" && info.LoadBalancedZone == "cloud.example.io"
				}))
			},
		},
		{
			name: "check nsNames",
			config: &Config{
				DNSZones:              "cloud.example.com: k8gb-test.gslb.cloud.example.com :60;",
				ParentZoneDNSServers:  []utils.DNSServer{{Host: "edge.com", Port: 53}},
				ClusterGeoTag:         "us",
				ExtClustersGeoTagsRaw: []string{"za", "eu"},
			},
			expectedLen: 1,
			assert: func(zoneInfo []*DelegationZoneInfo, err error) {
				assert.NoError(t, err)
				assert.True(t, zoneInfo[0].ClusterNSName == "gslb-ns-us-k8gb-test-gslb.cloud.example.com" &&
					zoneInfo[0].ExtClusterNSNames["za"] == "gslb-ns-za-k8gb-test-gslb.cloud.example.com" &&
					zoneInfo[0].ExtClusterNSNames["eu"] == "gslb-ns-eu-k8gb-test-gslb.cloud.example.com")
			},
		},

		{
			name: "nsName exceed exceed limit 253 characters",
			config: &Config{
				DNSZones:              fmt.Sprintf("cloud.example.com:k8gb-test.%s.gslb.cloud.example.com:60;", str220),
				ParentZoneDNSServers:  []utils.DNSServer{{Host: "edge.com", Port: 53}},
				ClusterGeoTag:         "us",
				ExtClustersGeoTagsRaw: []string{"za", "eu"},
			},
			expectedLen: 1,
			assert: func(_ []*DelegationZoneInfo, err error) {
				assert.Error(t, err)
			},
		},
		{
			name: "nsName label exceed limit 63 characters",
			config: &Config{
				DNSZones:              fmt.Sprintf("cloud.example.com: %s.gslb.cloud.example.com:60;", str64),
				ParentZoneDNSServers:  []utils.DNSServer{{Host: "edge.com", Port: 53}},
				ClusterGeoTag:         "us",
				ExtClustersGeoTagsRaw: []string{"za", "eu"},
			},
			expectedLen: 1,
			assert: func(_ []*DelegationZoneInfo, err error) {
				assert.Error(t, err)
			},
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			zoneInfo, err := parseDelegationZones(test.config)
			test.assert(zoneInfo, err)
			assert.Equal(t, test.expectedLen, len(zoneInfo))
		})
	}
}

func setEnvVarsFromString(envStr string) (cleanup func()) {
	lines := strings.Split(envStr, ";")
	// Map for storing old values
	var keys []string
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}
		parts := strings.SplitN(line, "=", 2)
		if len(parts) != 2 {
			continue // or return error
		}
		key := strings.TrimSpace(parts[0])
		val := strings.Trim(strings.TrimSpace(parts[1]), `"`)
		_ = os.Setenv(key, val)
		keys = append(keys, key)
	}
	// Cleanup function to restore
	return func() {
		for _, k := range keys {
			_ = os.Unsetenv(k)
		}
	}
}

// For circular references I can't use mocks in resolver. Thats why SpyClient exists
type SpyClient struct {
	client.Client
	UpdateCount int
	assertFunc  func(client.Object)
}

func NewSpyClient(obj *v1beta1.Gslb, a func(client.Object)) *SpyClient {
	scheme := runtime.NewScheme()
	_ = v1beta1.AddToScheme(scheme)
	cl := fake.NewClientBuilder().WithScheme(scheme).WithObjects(obj).Build()
	return &SpyClient{Client: cl, UpdateCount: 0, assertFunc: a}
}

func (s *SpyClient) Update(ctx context.Context, obj client.Object, opts ...client.UpdateOption) error {
	s.UpdateCount++
	s.assertFunc(obj)
	return s.Client.Update(ctx, obj, opts...)
}
